﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Web;

namespace Toolkit.Web.Infastructure
{
    /// <summary>
    /// Represents a class that is used by HTML helpers to build HTML elements.
    /// </summary>
    public sealed class TagBuilder
    {
        private const string _attributeFormat = @" {0}=""{1}""";
        private const string _elementFormatEndTag = "</{0}>";
        private const string _elementFormatNormal = "<{0}{1}>{2}</{0}>";
        private const string _elementFormatSelfClosing = "<{0}{1} />";
        private const string _elementFormatStartTag = "<{0}{1}>";

        private string _innerHtml;

        /// <summary>
        /// Initializes a new instance of the Web.Infastructure.TagBuilder class.
        /// </summary>
        /// <param name="tagName">The name of the tag.</param>
        public TagBuilder(string tagName)
        {
            if (String.IsNullOrEmpty(tagName))
            {
                throw new ArgumentException("tagName");
            }

            TagName = tagName;
            Attributes = new SortedDictionary<string, string>(StringComparer.Ordinal);
        }

        /// <summary>
        /// Gets or sets the collection of attributes for the tag.
        /// </summary>
        public IDictionary<string, string> Attributes
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the character that is used to replace the periods (dots) in the id attribute.
        /// </summary>
        public string IdAttributeDotReplacement
        {
            get
            {
                return System.Web.WebPages.Html.HtmlHelper.IdAttributeDotReplacement;
            }
        }

        /// <summary>
        /// Gets or sets the inner HTML for the tag (element).
        /// </summary>
        public string InnerHtml
        {
            get
            {
                return _innerHtml ?? String.Empty;
            }
            set
            {
                _innerHtml = value;
            }
        }

        /// <summary>
        /// Gets or sets the name of the tag.
        /// </summary>
        public string TagName
        {
            get;
            private set;
        }

        /// <summary>
        /// Adds the specified CSS class to the tag-builder attributes.
        /// </summary>
        /// <param name="value">The CSS class value string.</param>
        public void AddCssClass(string value)
        {
            string currentValue;

            if (Attributes.TryGetValue("class", out currentValue))
            {
                Attributes["class"] = value + " " + currentValue;
            }
            else
            {
                Attributes["class"] = value;
            }
        }

        /// <summary>
        /// Generates the id attribute for the tag by using the specified name.
        /// </summary>
        /// <param name="name">The name to use to generate the id value.</param>
        public void GenerateId(string name)
        {
            if (!String.IsNullOrEmpty(name))
            {
                MergeAttribute("id", name.Replace(".", IdAttributeDotReplacement));
            }
        }

        /// <summary>
        /// Generates attributes string for the tag.
        /// </summary>
        /// <returns>The attributes string to use to generate the tag.</returns>
        private string GetAttributesString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var attribute in Attributes)
            {
                string key = attribute.Key;
                string value = HttpUtility.HtmlAttributeEncode(attribute.Value);
                sb.AppendFormat(CultureInfo.InvariantCulture, _attributeFormat, key, value);
            }
            return sb.ToString();
        }

        /// <summary>
        /// Adds an attribute to the tag by using the specified key/value pair.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        public void MergeAttribute(string key, string value)
        {
            MergeAttribute(key, value, false);
        }

        /// <summary>
        /// Adds an attribute to the tag by using the specified key/value pair.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="value">The value.</param>
        /// <param name="replaceExisting">true to replace the existing attribute.</param>
        public void MergeAttribute(string key, string value, bool replaceExisting)
        {
            if (String.IsNullOrEmpty(key))
            {
                throw new ArgumentException("key");
            }

            if (replaceExisting || !Attributes.ContainsKey(key))
            {
                Attributes[key] = value;
            }
        }

        /// <summary>
        /// Adds an attribute to the specified collection of attributes for the tag.
        /// </summary>
        /// <typeparam name="TKey">The type of the key.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="attributes">The attributes.</param>
        public void MergeAttributes<TKey, TValue>(IDictionary<TKey, TValue> attributes)
        {
            MergeAttributes(attributes, false);
        }

        /// <summary>
        /// Adds an attribute to the specified collection of attributes for the tag.
        /// </summary>
        /// <typeparam name="TKey">The type of the key.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="attributes">The attributes.</param>
        /// <param name="replaceExisting">true to replace the existing attribute.</param>
        public void MergeAttributes<TKey, TValue>(IDictionary<TKey, TValue> attributes, bool replaceExisting)
        {
            if (attributes != null)
            {
                foreach (var entry in attributes)
                {
                    string key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
                    string value = Convert.ToString(entry.Value, CultureInfo.InvariantCulture);
                    MergeAttribute(key, value, replaceExisting);
                }
            }
        }

        /// <summary>
        /// Sets the inner text of the tag (element).
        /// </summary>
        /// <param name="innerText">The inner text for the tag (element).</param>
        public void SetInnerText(string innerText)
        {
            InnerHtml = HttpUtility.HtmlEncode(innerText);
        }

        /// <summary>
        /// Returns a string that represents the current object.
        /// </summary>
        /// <returns>A string that represents the current object.</returns>
        public override string ToString()
        {
            return ToString(TagRenderMode.Normal);
        }

        /// <summary>
        /// Returns a string that represents the current object by using the specified tag-render mode.
        /// </summary>
        /// <param name="renderMode">The tag-render mode.</param>
        /// <returns>A string that represents the current object.</returns>
        public string ToString(TagRenderMode renderMode)
        {
            switch (renderMode)
            {
                case TagRenderMode.StartTag:
                    return String.Format(CultureInfo.InvariantCulture, _elementFormatStartTag, TagName, GetAttributesString());
                case TagRenderMode.EndTag:
                    return String.Format(CultureInfo.InvariantCulture, _elementFormatEndTag, TagName);
                case TagRenderMode.SelfClosing:
                    return String.Format(CultureInfo.InvariantCulture, _elementFormatSelfClosing, TagName, GetAttributesString());
                default:
                    return String.Format(CultureInfo.InvariantCulture, _elementFormatNormal, TagName, GetAttributesString(), InnerHtml);
            }
        }
    }
}
